﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;

namespace MvvmFx.Common.ViewModels
{
    /// <summary>
    /// The base class for all ViewModel classes. It implements <see cref="INotifyPropertyChanged"/>
    /// and exposes a Key property which can be used by <see cref="ViewModelCollection"/>
    /// </summary>
    /// <remarks>
    /// The Key property is optional, and is only used by derived ViewModels that need
    /// to be distinguished by a value in collections, or by storing an id or data about
    /// the model entity it corresponds to.
    /// </remarks>
    public abstract partial class ViewModelBase : INotifyPropertyChanged
    {
        #region Members

        /// <summary>
        /// The key field.
        /// </summary>
        private string key;

        /// <summary>
        /// If to throw invalid property name state field.
        /// </summary>
        private static bool _throwOnInvalidPropertyName = false;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModelBase"/> class.
        /// </summary>
        protected ViewModelBase()
        {

        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModelBase"/> class.
        /// </summary>
        /// <param name="key">The key.</param>
        protected ViewModelBase(string key)
            : this()
        {
            this.key = key;
        }

        #endregion

        #region Events

        /// <summary>
        /// The event PropertyChanged of type PropertyChangedEventHandler.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the value of the <see cref=”Key”/> property.
        /// </summary>
        public string Key
        {
            get { return key; }
            set
            {
                if (key != value)
                {
                    key = value;
                    OnPropertyChanged("Key");
                }
            }
        }

        /// <summary>
        /// Gets or sets the value of the <see cref="ThrowOnInvalidPropertyName"/> property.
        /// </summary>
        /// <value>If to throw and execption on an invalid property name.</value>
        public static bool ThrowOnInvalidPropertyName
        {
            get { return _throwOnInvalidPropertyName; }
            set { _throwOnInvalidPropertyName = value; }
        }

        #endregion

        #region Notify PropertyChanged

        /// <summary>
        /// Verify that the property name name exists on the type.
        /// </summary>
        /// <param name="propertyName">The name of the property.</param>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        private void VerifyPropertyName(string propertyName)
        {
            // Verify that the property name matches a real,  
            // public, instance property on this object.
            //
            if (_throwOnInvalidPropertyName)
            {
                if (!string.IsNullOrEmpty(propertyName))
                {
                    var type = this.GetType();
                    var propInfo = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);

                    if (propInfo == null)
                    {
                        throw new ArgumentOutOfRangeException(propertyName);
                    }

                    string msg = "Invalid property name: " + propertyName;
                    Debug.WriteLine(msg);
                }
            }
        }

        #endregion

        #region Refresh Bindings

        /// <summary>
        /// Refreshes the the bindings to properties of this instance.
        /// </summary>
        public void Refresh()
        {
            OnRefresh("");
        }

        /// <summary>
        /// Refreshes the bindings to the specified property name.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        public void Refresh(string propertyName)
        {
            OnRefresh(propertyName);
        }

        /// <summary>
        /// Refreshes the bindings to the specified property name.
        /// </summary>
        protected void OnRefresh(string propertyName)
        {
            this.OnPropertyChanged(propertyName);
        }

        #endregion

        #region ToString

        /// <summary>
        /// A string represents the current object. 
        /// </summary>
        /// <returns>A <see cref="String"/> that represents the current <see cref="Object"/>.</returns>
        public override string ToString()
        {
            return string.Format("{0}: {1}", this.GetType().Name, key);
        }

        #endregion
    }
}